/*******************************************************************************
 * Copyright (c) 2017-2020, org.smartboot. All rights reserved.
 * project name: smart-http
 * file name: StringUtils.java
 * Date: 2020-01-01
 * Author: sandao (zhengjunweimail@163.com)
 ******************************************************************************/
package org.smartboot.http.utils;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;

public class StringUtils {

	/**
	 * An empty immutable {@code String} array.
	 */
	public static final String[] EMPTY_STRING_ARRAY = new String[0];
	/**
	 * The empty String {@code ""}.
	 *
	 * @since 2.0
	 */
	public static final String EMPTY = "";

	/**
	 * Represents a failed index search.
	 *
	 * @since 2.1
	 */
	public static final int INDEX_NOT_FOUND = -1;
	public static final List<StringCache>[] String_CACHE_URL = new List[512];
	public static final List<StringCache>[] String_CACHE_HEADER_NAME = new List[32];
	public static final List<StringCache>[] String_CACHE_HEADER_VALUE = new List[512];

	static {
		for (int i = 0; i < String_CACHE_URL.length; i++) {
			String_CACHE_URL[i] = new ArrayList<>(8);
		}
		for (int i = 0; i < String_CACHE_HEADER_NAME.length; i++) {
			String_CACHE_HEADER_NAME[i] = new ArrayList<>(8);
		}
		for (int i = 0; i < String_CACHE_HEADER_VALUE.length; i++) {
			String_CACHE_HEADER_VALUE[i] = new ArrayList<>(8);
		}
	}

	public StringUtils() {
		super();
	}

	public static String convertToString(char[] bytes, int length, List<StringCache>[] cacheList) {
		return convertToString(bytes, 0, length, cacheList);
	}

	public static String trim(final String str) {
		return str == null ? null : str.trim();
	}

	public static String convertToString(char[] bytes, int offset, int length, List<StringCache>[] cacheList) {
		if (length == 0) {
			return "";
		}
		if (length >= cacheList.length) {
			return new String(bytes, offset, length);
		}
		List<StringCache> list = cacheList[length];
		for (int i = list.size() - 1; i > -1; i--) {
			StringCache cache = list.get(i);
			if (equals(cache.bytes, bytes, offset)) {
				return cache.value;
			}
		}
		synchronized (list) {
			for (StringCache cache : list) {
				if (equals(cache.bytes, bytes, offset)) {
					return cache.value;
				}
			}
			String str = new String(bytes, offset, length);
			list.add(new StringCache(str.toCharArray(), str));
			return str;
		}
	}

	private static boolean equals(char[] b0, char[] b1, int offset) {
		for (int i = b0.length - 1; i > 0; i--) {
			if (b0[i] != b1[i + offset]) {
				return false;
			}
		}
		return b0[0] == b1[offset];
	}

	public static boolean isEmpty(final CharSequence cs) {
		return cs == null || cs.length() == 0;
	}

	public static boolean isBlank(final CharSequence cs) {
		int strLen;
		if (cs == null || (strLen = cs.length()) == 0) {
			return true;
		}
		for (int i = 0; i < strLen; i++) {
			if (!Character.isWhitespace(cs.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	public static boolean isNotBlank(final CharSequence cs) {
		return !StringUtils.isBlank(cs);
	}

	public static boolean equals(final CharSequence cs1, final CharSequence cs2) {
		if (cs1 == cs2) {
			return true;
		}
		if (cs1 == null || cs2 == null) {
			return false;
		}
		if (cs1 instanceof String && cs2 instanceof String) {
			return cs1.equals(cs2);
		}
		return regionMatches(cs1, false, 0, cs2, 0, Math.max(cs1.length(), cs2.length()));
	}

	private static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, final CharSequence substring, final int start, final int length) {
		if (cs instanceof String && substring instanceof String) {
			return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
		} else {
			int index1 = thisStart;
			int index2 = start;
			int tmpLen = length;

			while (tmpLen-- > 0) {
				char c1 = cs.charAt(index1++);
				char c2 = substring.charAt(index2++);

				if (c1 == c2) {
					continue;
				}

				if (!ignoreCase) {
					return false;
				}

				// The same check as in String.regionMatches():
				if (Character.toUpperCase(c1) != Character.toUpperCase(c2) && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
					return false;
				}
			}

			return true;
		}
	}

	public static String substring(final String str, int start) {
		if (str == null) {
			return null;
		}

		if (start < 0) {
			start = str.length() + start;
		}

		if (start < 0) {
			start = 0;
		}
		if (start > str.length()) {
			return EMPTY;
		}

		return str.substring(start);
	}

	public static String substring(final String str, int start, int end) {
		if (str == null) {
			return null;
		}

		if (end < 0) {
			end = str.length() + end;
		}
		if (start < 0) {
			start = str.length() + start;
		}

		if (end > str.length()) {
			end = str.length();
		}

		if (start > end) {
			return EMPTY;
		}

		if (start < 0) {
			start = 0;
		}
		if (end < 0) {
			end = 0;
		}

		return str.substring(start, end);
	}

	public static String substringBefore(final String str, final String separator) {
		if (isEmpty(str) || separator == null) {
			return str;
		}
		if (separator.isEmpty()) {
			return EMPTY;
		}
		final int pos = str.indexOf(separator);
		if (pos == INDEX_NOT_FOUND) {
			return str;
		}
		return str.substring(0, pos);
	}

	public static String substringAfter(final String str, final String separator) {
		if (isEmpty(str)) {
			return str;
		}
		if (separator == null) {
			return EMPTY;
		}
		final int pos = str.indexOf(separator);
		if (pos == INDEX_NOT_FOUND) {
			return EMPTY;
		}
		return str.substring(pos + separator.length());
	}

	public static String[] split(final String str, final String separatorChars) {
		return splitWorker(str, separatorChars, -1, false);
	}

	public static String[] splitPreserveAllTokens(final String str, final String separatorChars) {
		return splitWorker(str, separatorChars, -1, true);
	}

	private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) {

		if (str == null) {
			return null;
		}
		final int len = str.length();
		if (len == 0) {
			return EMPTY_STRING_ARRAY;
		}
		final List<String> list = new ArrayList<String>();
		int sizePlus1 = 1;
		int i = 0, start = 0;
		boolean match = false;
		boolean lastMatch = false;
		if (separatorChars == null) {
			// Null separator means use whitespace
			while (i < len) {
				if (Character.isWhitespace(str.charAt(i))) {
					if (match || preserveAllTokens) {
						lastMatch = true;
						if (sizePlus1++ == max) {
							i = len;
							lastMatch = false;
						}
						list.add(str.substring(start, i));
						match = false;
					}
					start = ++i;
					continue;
				}
				lastMatch = false;
				match = true;
				i++;
			}
		} else if (separatorChars.length() == 1) {
			// Optimise 1 character case
			final char sep = separatorChars.charAt(0);
			while (i < len) {
				if (str.charAt(i) == sep) {
					if (match || preserveAllTokens) {
						lastMatch = true;
						if (sizePlus1++ == max) {
							i = len;
							lastMatch = false;
						}
						list.add(str.substring(start, i));
						match = false;
					}
					start = ++i;
					continue;
				}
				lastMatch = false;
				match = true;
				i++;
			}
		} else {
			// standard case
			while (i < len) {
				if (separatorChars.indexOf(str.charAt(i)) >= 0) {
					if (match || preserveAllTokens) {
						lastMatch = true;
						if (sizePlus1++ == max) {
							i = len;
							lastMatch = false;
						}
						list.add(str.substring(start, i));
						match = false;
					}
					start = ++i;
					continue;
				}
				lastMatch = false;
				match = true;
				i++;
			}
		}
		if (match || preserveAllTokens && lastMatch) {
			list.add(str.substring(start, i));
		}
		return list.toArray(new String[list.size()]);
	}

	public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {

		if (str == null) {
			return null;
		}
		StringTokenizer st = new StringTokenizer(str, delimiters);
		List<String> tokens = new ArrayList<String>();
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			if (trimTokens) {
				token = token.trim();
			}
			if (!ignoreEmptyTokens || token.length() > 0) {
				tokens.add(token);
			}
		}
		return toStringArray(tokens);
	}

	public static String[] toStringArray(Collection<String> collection) {
		if (collection == null) {
			return null;
		}
		return collection.toArray(new String[collection.size()]);
	}

	public static int length(final CharSequence cs) {
		return cs == null ? 0 : cs.length();
	}

	public static boolean startsWith(final CharSequence str, final CharSequence prefix) {
		return startsWith(str, prefix, false);
	}

	private static boolean startsWith(final CharSequence str, final CharSequence prefix, final boolean ignoreCase) {
		if (str == null || prefix == null) {
			return str == null && prefix == null;
		}
		if (prefix.length() > str.length()) {
			return false;
		}
		return regionMatches(str, ignoreCase, 0, prefix, 0, prefix.length());
	}

	public static boolean endsWith(final CharSequence str, final CharSequence suffix) {
		return endsWith(str, suffix, false);
	}

	private static boolean endsWith(final CharSequence str, final CharSequence suffix, final boolean ignoreCase) {
		if (str == null || suffix == null) {
			return str == null && suffix == null;
		}
		if (suffix.length() > str.length()) {
			return false;
		}
		final int strOffset = str.length() - suffix.length();
		return regionMatches(str, ignoreCase, strOffset, suffix, 0, suffix.length());
	}

	public static int scanUntilAndTrim(ByteBuffer buffer, byte split, char[] cacheChars, int charsIndex, boolean trim) {
		buffer.mark();
		int i = 0;
		if (trim) {
			while ((cacheChars[charsIndex] = (char) (buffer.get() & 0xFF)) == Constant.SP)
				;
			i = 1;
		}
		while (buffer.hasRemaining()) {
			cacheChars[charsIndex + i] = (char) (buffer.get() & 0xFF);
			if (cacheChars[charsIndex + i] == split) {
				//反向去空格
				while (trim && cacheChars[charsIndex + i - 1] == Constant.SP) {
					i--;
				}
				return i;
			}
			i++;
		}
		buffer.reset();
		return -1;
	}

	public static int scanUntilAndTrim(ByteBuffer buffer, byte split, char[] cacheChars, boolean trim) {
		return scanUntilAndTrim(buffer, split, cacheChars, 0, trim);
	}

	static class StringCache {
		final char[] bytes;
		final String value;

		public StringCache(char[] bytes, String value) {
			this.bytes = bytes;
			this.value = value;
		}
	}

}
